home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Utilities / Workspace / Briefcase / Source / MultDoc.m < prev    next >
Text File  |  1995-06-12  |  14KB  |  544 lines

  1. #import "MultDoc.h"
  2. #import "MultApp.h"
  3. #import <appkit/appkit.h>
  4.  
  5. @implementation MultDoc: Responder
  6.  
  7. /* Canon Information Systems is not responsible for anything anyone does with this   */
  8. /* code, nor are they responsible for the correctness of this code.  Basically, this */
  9. /* has very little to do with the company I work for, and you can't blame them.      */
  10.  
  11. /* This file is best read in a window as wide as this comment, and with tab settings */
  12. /* of 4 spaces and indent setting of 4 spaces (at least, that's what I use).         */
  13.  
  14. /* You are welcome to do as you would with this file under the following conditions. */
  15. /* First, I accept no blame for anything that goes wrong no matter how you use it,   */
  16. /* no matter how catastrophic, not even if it stems from a bug in my code.             */
  17. /* Second, please keep my notices on it when/if you distribute it.                     */
  18. /* Third, if you discover any bugs or have any comments, PLEASE TELL ME!  Code won't */
  19. /* get better without people picking it apart and giving the writer feedback.        */
  20. /* Fourth, if you modify it, please keep a notice that your version is based on mine */
  21. /* in the source files (and keep the notice that mine is based on four other pieces  */
  22. /* of code :<).  Thanks, and have fun.  - Subrata Sircar, ssircar@canon.com             */
  23.  
  24. /* This class should handle all the document-level responsibilities   */
  25. /* that a nice document in a multi-doc environment should handle:     */
  26. /* creation, deletion, keeping track of dirty bits, not closing w/out */
  27. /* allowing the user to save, etc.  Most of this code is lifted from  */
  28. /* DrawDocument, or TextLab; heavy use is made of the firstResponder  */
  29. /* mechanism.  Thanks again, guys.                                       */
  30.  
  31. /* The default implementation loads windows with scrollviews from a nib file. */
  32. /* To modify, the revert/save/newFromFile methods should be changed to use    */
  33. /* the view (subclass) that acts as the content view of the Window.           */
  34. /*                    - Subrata Sircar (ssircar@canon.com)                      */
  35.  
  36. /* Version 0.9b            Apr-19-92        First Public Release    */
  37. /* Version 0.95b        Apr-29-92        Multiple Save Types        */
  38. /* Version 1.0b            Aug-10-92        Minor Bug Fixes            */
  39.  
  40. /* Factory (Class) variables */
  41.  
  42. static char         *default_format        = NULL;    /* Format string for default title */
  43. static const char     *extension            = NULL;    /* Document extension */
  44. static const int     myVersion            = 100;    /* version number * 100 */
  45. static id             zoneList             = nil;
  46. static NXCoord         ORIGX                 = 100.0;/* doc starting position and increment */
  47. static NXCoord         ORIGY                 = 100.0;
  48. static int            posInc                 = 20.0;
  49.  
  50. + initialize
  51. /* Class variable initialization.  DO NOT call from subclasses. */
  52. {
  53.     if (self == [MultDoc class]) {
  54.         [self setVersion:myVersion];
  55.         [self setExtension:LocalString("txt")];
  56.         [self setDefault:LocalString("Untitled%d")];
  57.     }
  58.     return self;
  59. }
  60.  
  61. /* Extension to use in Workspace file names */
  62.  
  63. + setExtension:(const char *)newExtension
  64. {
  65.     if (extension) NX_FREE(extension);
  66.     extension = NXCopyStringBufferFromZone(newExtension,MyZone);
  67.     return self;
  68. }
  69.  
  70. + (const char *)extension
  71. {
  72.     return extension;
  73. }
  74.  
  75. /* Default Title string for new documents */
  76.  
  77. + setDefault:(const char *)newDefault
  78. {
  79.     char temp[MAXPATHLEN+1];
  80.     
  81.     if (default_format) NX_FREE(default_format);
  82.     sprintf(temp,"%s.%s",newDefault,[[self class] extension]);
  83.     default_format = NXCopyStringBufferFromZone(temp,MyZone);
  84.     return self;
  85. }
  86.  
  87. + (const char *)default
  88. {
  89.     return default_format;
  90. }
  91.  
  92. /* Factory zone methods.  I adopt the virtual zone strategy used by Draw. */
  93.  
  94. + (NXZone *)newZone
  95. {
  96.     if (!zoneList || ![zoneList count]) {
  97.         return NXCreateZone(vm_page_size, vm_page_size, YES);
  98.     } else {
  99.         return (NXZone *)[zoneList removeLastObject];
  100.     }
  101. }
  102.  
  103. + (void)reuseZone:(NXZone *)aZone
  104. {
  105.     if (!zoneList) zoneList = [List new];
  106.     [zoneList addObject:(id)aZone];
  107.     NXNameZone(aZone, "Unused");
  108. }
  109.  
  110. + allocFromZone:(NXZone *)aZone
  111. {
  112.     return [self notImplemented:@selector(allocFromZone:)];
  113. }
  114.  
  115. + alloc
  116. {
  117.     return [self notImplemented:@selector(alloc)];
  118. }
  119.  
  120.  
  121. /* New creation methods */
  122.  
  123. - setUpNib
  124. {
  125.     LoadLocalNib(LocalString("MultDoc.nib"),self,NO,MyZone);
  126.     [[view docView] setDelegate:self];
  127.     [window makeFirstResponder:[view docView]];
  128.     return self;
  129. }
  130.  
  131. - instanceAwake
  132. {
  133.     NXRect frameRect;
  134.     calcFrame(printInfo, &frameRect);
  135.     if (frameRect.size.width > 1000) frameRect.size.width = 1000;
  136.     if (frameRect.size.height > 700) frameRect.size.height = 700;
  137.     [window sizeWindow:frameRect.size.width :frameRect.size.height];
  138.     [window moveTo:ORIGX :ORIGY];
  139.     ORIGX += posInc; ORIGY -= posInc;
  140.     if (ORIGX > 700) ORIGX = 110;
  141.     if (ORIGY < 100) ORIGY = 160;
  142.     [window setDelegate:self];
  143.     [self dirty:NO];
  144.     [self setSavedDocument:NO];
  145.     [self setEmpty:YES];
  146.     [self setName:NULL andDirectory:NULL];
  147.     [window makeKeyAndOrderFront:self];
  148.     return self;
  149. }
  150.  
  151. + newFromFile:(const char *)file
  152. {
  153.     NXStream *stream;
  154.     
  155.     stream = NXMapFile(file,NX_READONLY);
  156.     if (stream) {
  157.         self = [self new];
  158.         [[view docView] readText:stream];
  159.         [[view docView] sizeToFit];
  160.         [view display];
  161.         [self setName:file];
  162.         [self setSavedDocument:YES];
  163.         [self setEmpty:NO];
  164.         [window makeKeyAndOrderFront:self];
  165.         NXCloseMemory(stream,NX_FREEBUFFER);
  166.         return self;
  167.     } else {
  168.         Notify(LocalString("MultDoc: can't open file"),file);
  169.         return nil;
  170.     }
  171. }
  172.  
  173. + new
  174. {
  175.     self = [[self class] newFromZone:[[self class] newZone]];
  176.     return self;
  177. }
  178.  
  179. + newFromZone:(NXZone *)zone;
  180. {
  181.     self = [super allocFromZone:zone];
  182.     printInfo = [PrintInfo new];
  183.     [printInfo setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0];
  184.     [self setUpNib];
  185.     [self instanceAwake];
  186.     return self;
  187. }
  188.  
  189. - free
  190. {
  191.  
  192.     NX_FREE(name);
  193.     NX_FREE(directory);
  194.     [printInfo free];
  195.     [window free];
  196.     [[self class] reuseZone:MyZone];
  197.     return [super free];
  198. }
  199.  
  200. - view
  201. /*
  202.  * Returns the view associated with this document.
  203.  */
  204. {
  205.     return view;
  206. }
  207.  
  208. - (BOOL)needsSaving
  209. /*
  210.  * Returns the dirty BOOL associated with this document.
  211.  */
  212. {
  213.     return dirty;
  214. }
  215.  
  216. - (BOOL)isEmpty
  217. /*
  218.  * Returns the empty BOOL associated with this document.
  219.  */
  220. {
  221.     return empty;
  222. }
  223.  
  224. - (BOOL)hasSavedDocument
  225. {
  226.     return haveSavedDocument;
  227. }
  228.  
  229. - setEmpty:(BOOL)flag
  230. /*
  231.  * Sets the BOOL to flag.
  232.  */
  233. {
  234.     empty = flag;
  235.     return self;
  236. }
  237.  
  238. - dirty:(BOOL)flag
  239. /*
  240.  * Sets the BOOL to flag.
  241.  */
  242. {
  243.     dirty = flag;
  244.     [window setDocEdited:flag];
  245.     return self;
  246. }
  247.  
  248. - setSavedDocument:(BOOL)flag
  249. /*
  250.  * Sets the BOOL to flag.
  251.  */
  252. {
  253.     haveSavedDocument = flag;
  254.     return self;
  255. }
  256.  
  257. - printInfo
  258. {
  259.     return printInfo;
  260. }
  261.  
  262.  
  263. /* Target/Action methods */
  264.  
  265. - save:sender
  266. /*
  267.  * Saves the file.  If this document has never been saved to disk,
  268.  * then a SavePanel is put up to ask the user what file name she
  269.  * wishes to use to save the document.
  270.  */
  271. {
  272.     id            del            = [NXApp delegate];
  273.     id            savepanel    = [del saveAsPanel:self];
  274.     const char    *dir        = NULL;    
  275.  
  276.     if (![self hasSavedDocument]) {
  277.         if ([savepanel runModalForDirectory:directory file:name])
  278.             [self setName:[savepanel filename]];
  279.         else return nil;
  280.     }
  281.  
  282.     [self save];
  283.     dir = [self directory];
  284.     chdir(dir);
  285.     [del setDefaultDir:dir];
  286.     return self;
  287. }
  288.  
  289. - saveAs:sender
  290. /* Changes the document name.  IF you cancel, it does the right thing     */
  291. /* and undoes the changes to the state bits.                            */
  292. {
  293.     BOOL oldSavedFile, oldDirty;
  294.     
  295.     oldDirty = [self needsSaving];
  296.     oldSavedFile = [self hasSavedDocument];
  297.     [self dirty:YES];
  298.     [self setSavedDocument:NO];
  299.     if ([self save:sender]) return self;
  300.     else {
  301.         [self setSavedDocument:oldSavedFile];
  302.         [self dirty:oldDirty];
  303.         return nil;
  304.     }
  305. }
  306.  
  307. - revertToSaved:sender
  308. /*
  309.  * Revert the document back to what is on the disk.
  310.  */ 
  311. {
  312.     MultDoc *oldPtr;
  313.     
  314.     if (![self hasSavedDocument] ||
  315.         ![self needsSaving] ||
  316.         (NXRunAlertPanel(LocalString("Revert"),
  317.                          LocalString("%s has been edited.  Are you sure you want to undo changes?"),
  318.                          LocalString("Revert"),
  319.                          LocalString("Cancel"), NULL, name) != NX_ALERTDEFAULT)) {
  320.     return self;
  321.     }
  322.  
  323.     oldPtr = self;
  324.     self = [[oldPtr class] newFromFile:[oldPtr fileName]];
  325.     [oldPtr free];
  326.     return self;
  327. }
  328.  
  329. - save
  330. {    
  331.     NXStream *ts;
  332.     int fd;
  333.     
  334.     fd = open([self fileName], O_CREAT | O_WRONLY | O_TRUNC, 0666);
  335.     ts = NXOpenFile(fd, NX_WRITEONLY);
  336.     if (ts) {
  337.         [[view docView] writeText:ts];
  338.         NXFlush(ts);
  339.         NXClose(ts);
  340.         [self dirty:NO];
  341.         [self setSavedDocument:YES];
  342.     } else Notify(LocalString("MultDoc:save - can't create file"),[self fileName]);
  343.     close(fd);
  344.     return self;
  345. }
  346.  
  347. - changeLayout:sender
  348. /*
  349.  * Puts up a PageLayout panel and allows the user to pick a different
  350.  * size paper to work on.  After she does so, the view is resized to the
  351.  * new paper size.
  352.  * Since the PrintInfo is effectively part of the document, we dirty
  353.  * the view (by performing the dirty method).
  354.  */
  355. {
  356.     NXRect frame;
  357.  
  358.     if ([[[NXApp delegate] pageLayout:self] runModal] == NX_OKTAG) {
  359.         calcFrame(printInfo, &frame);
  360.         [window sizeWindow:frame.size.width :frame.size.height];
  361.         [self dirty:YES];
  362.         [window display];
  363.     }
  364.  
  365.     return self;
  366. }
  367.  
  368.  
  369. /* Methods related to naming/saving this document. */
  370.  
  371. - (const char *)fileName
  372. /*
  373.  * Gets the fully specified file name of the document.
  374.  * If directory is NULL, then the currentDirectory is used.
  375.  * If name is NULL, then the default title is used.
  376.  */
  377. {
  378.     static char filenamebuf[MAXPATHLEN+1];
  379.  
  380.     if (!directory && !name) [self setName:NULL andDirectory:NULL];
  381.     if (directory) {
  382.         strcpy(filenamebuf, directory);
  383.         strcat(filenamebuf, "/");
  384.     } else filenamebuf[0] = '\0';
  385.     if (name) strcat(filenamebuf, name);
  386.  
  387.     return filenamebuf;
  388. }
  389.  
  390. - (const char *)directory
  391. {
  392.     return directory;
  393. }
  394.  
  395. - (const char *)name
  396. {
  397.     return name;
  398. }
  399.  
  400. - setName:(const char *)newName andDirectory:(const char *)newDirectory
  401. /*
  402.  * Updates the name and directory of the document.
  403.  * newName or newDirectory can be NULL, in which case the name or directory
  404.  * will not be changed (unless one is currently not set, in which case
  405.  * a default name will be used).
  406.  */
  407. {
  408.     char newNameBuf[MAXPATHLEN+1];
  409.     static int uniqueCount = 1;
  410.  
  411.     if ((newName && *newName) || !name) {
  412.         if (!newName || !*newName) {
  413.             sprintf(newNameBuf, [[self class] default], uniqueCount++);
  414.             newName = newNameBuf;
  415.         } else if (name) NX_FREE(name);
  416.         name = NXCopyStringBufferFromZone(newName, MyZone);
  417.     }
  418.  
  419.     if ((newDirectory && (*newDirectory == '/')) || !directory) {
  420.         if (!newDirectory || (*newDirectory != '/')) newDirectory = [[NXApp delegate] currentDirectory];
  421.         else if (directory) NX_FREE(directory);
  422.         directory = NXCopyStringBufferFromZone(newDirectory, MyZone);
  423.     }
  424.  
  425.     [window setTitleAsFilename:[self fileName]];
  426.     NXNameZone(MyZone, [self fileName]);
  427.  
  428.     return self;
  429. }
  430.  
  431. - setName:(const char *)file
  432. /*
  433.  * If file is a full path name, then both the name and directory of the
  434.  * document is updated appropriately, otherwise, only the name is changed.
  435.  */
  436. {
  437.     char *lastComponent;
  438.     char path[MAXPATHLEN+1];
  439.  
  440.     if (file) {
  441.         strcpy(path, file);
  442.         lastComponent = rindex(path, '/');
  443.         if (lastComponent) {
  444.             *lastComponent++ = '\0';
  445.             return [self setName:lastComponent andDirectory:path];
  446.         } else return [self setName:file andDirectory:NULL];
  447.     }
  448.  
  449.     return self;
  450. }
  451.  
  452. /* Window delegate methods. */
  453.  
  454. - windowWillClose:sender
  455. /*
  456.  * If the document has been edited, then this asks the user if she
  457.  * wants to save the changes before closing the window.  When the window
  458.  * is closed, the document itself must be freed.  This is accomplished
  459.  * via Application's delayedFree: mechanism.  Unfortunately, by the time
  460.  * delayedFree: frees the document, the window and view instance variables
  461.  * will already have automatically been freed by virtue of the window's being
  462.  * closed.  Thus, those instance variables must be set to nil to avoid their
  463.  * being freed twice.  (This behavior is set in the window flags in IB.)
  464.  *
  465.  * Returning nil from this method informs the caller that the window should
  466.  * NOT be closed.  Anything else implies it should be closed.
  467.  */
  468. {
  469.     int save;
  470.  
  471.     if ([self needsSaving] && ![self isEmpty]) {
  472.         [window orderFront:self];
  473.         save = NXRunAlertPanel(LocalString("Close"),
  474.                                LocalString("%s has changes. Save them?"),
  475.                                LocalString("Save"),
  476.                                LocalString("Don't Save"),
  477.                                LocalString("Cancel"),
  478.                                name);
  479.         if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) return nil;
  480.         else {
  481.             [window endEditingFor:self];    /* terminate any editing */
  482.             if (save == NX_ALERTDEFAULT) {
  483.                 if (![self save:nil]) return nil;
  484.             }
  485.         }
  486.     }
  487.  
  488.     window = nil;
  489.     view = nil;
  490.     if (printInfo) [NXApp setPrintInfo:nil];
  491.     [NXApp delayedFree:self];
  492.     chdir([[NXApp delegate] currentDirectory]);
  493.     return self;
  494. }
  495.  
  496. - windowDidBecomeMain:sender
  497. /*
  498.  * Switch the Application's PrintInfo to the document's when the document
  499.  * window becomes the main window.  Also set the cursor appropriately
  500.  * depending on which tool is currently selected.
  501.  */
  502. {
  503.     [NXApp setPrintInfo:printInfo];
  504.     return self;
  505. }
  506.  
  507. - windowWillMiniaturize:sender toMiniwindow:counterpart
  508. {
  509.     char *dot;
  510.     char title[MAXPATHLEN+1];
  511.  
  512.     strcpy(title, [self name]);
  513.     dot = rindex(title, '.');
  514.     if (dot && !strcmp(++dot, extension)) *(--dot) = '\0';
  515.     [counterpart setTitle:title];
  516.     return self;
  517. }
  518.  
  519.  
  520. /* Validates whether a menu command makes sense now */
  521.  
  522. - (BOOL)validateCommand:menuCell
  523. {
  524.     SEL action = [menuCell action];
  525.  
  526.     if (action == @selector(revertToSaved:)) return ([self needsSaving] && [self hasSavedDocument]);
  527.     else if (action == @selector(saveAs:)) return (![self isEmpty] && [self hasSavedDocument]);
  528.     else if (action == @selector(close:)) return YES;
  529.      
  530.     return YES;
  531. }
  532.  
  533. /* Text Delegate methods */
  534.  
  535. - textDidGetKeys:sender isEmpty:(BOOL)flag
  536. {
  537.  
  538.     if (![self needsSaving]) [self dirty:YES];
  539.     [self setEmpty:flag];
  540.     return self;
  541. }
  542.  
  543. @end
  544.